/**
This file is provided to you under the Apache License,
Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain
a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
**/

var jqstore = { 
	get: function() { alert('jqstore:get'); },
	put: function(data) { };
	fetch: function(data) {
	};
	obj: function(members) {};
	member:function(name,data) {};
	value:function(data) {};
	array:function(data) {};
};

var query = {
		
};
var anId = 'some-relevant-id';
var testUser = 
{ 
	user: anId,
	name:"Billy Bob", 
	itemsId: anId,
};
var query1 = jqstore.query().search().page(0,10).options().id().uri().collection().constrain("id",someid).orderby()
	{
		someId:anId,
		user:"{/user[id=$someId]}",
		items:"{/item[id = $user/itemsId]}",
		_search_string:"some search string",
		_search_options:"values,names",
		_id:"some-id",
		_class:"class-name", // like a collection in ML. type?
		_restrictions:"??????",
		_result:
			jqstore.object(
				"user/*", 
				jqstore.member("items",items)
			)
	}
);

/**
* This is a Javascript client for the Riak REST API. It
* has two dependencies:
* Douglas Crockford's JSON library: http://www.json.org/js.html
* jQuery: http://jquery.com/ (but only for Ajax requests)
*
*/

/**
* TODO: Handle sibling values
*/

/**
* Utility functions which don't belong anywhere else
*/
var RiakUtil = function() {
  return {
    /**
* Base64 encode a number
* @param num - Number to encode
* @return string containing base64 encoded number
*/
    base64Encode: function(num) {
      var base64digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        return base64digits[(num >>> 26)] + base64digits[((num >>> 20)&63)] +
               base64digits[((num >>> 14)&63)] + base64digits[((num >>> 8)&63)] +
               base64digits[((num >>> 2)&63)] + base64digits[((num << 4)&63)] + '==';
    },
    /**
* Trim spaces from beginning/end of text
* @param text - Text to trimg
* @return string with leading & trailing spaces removed
*/
    trim: function(text) {
      var tmp = text.replace(/^\s+/, '');
      return tmp.replace(/\s+$/, '');
    },

    /**
* Was request successful?
* @param req- XMLHttpRequest object
* @return true if status is 2xx, false otherwise
*/
    wasSuccessful: function(req) {
      return req.status > 199 && req.status < 300;
    },
    /**
* Create a modified accepts object
*/
    multipart_accepts: function() {
      var current = jQuery.ajaxSettings.accepts;
      var accepts = {};
      for (prop in current) {
        accepts[prop] = current[prop];
      }
      accepts.multipart = "multipart/mixed;q=1.1";
      return accepts;
    },
    get_boundary: function(contentType) {
      var idx = contentType.indexOf("boundary=");
      if(idx < 0)
        throw('Could not locate boundary for multipart/mixed');
      return contentType.substr(idx+9);
    },
    /**
* Parse a 300 request into siblings. This handles embedded
* new lines and control characters. Unfortunately Firefox
* seems to trim embedded \000 in the XHR response. Beware
* for binary data (images etc) you may need to set allow_mult
* false for the bucket until an alternative is found.
*
* @param contentType content type header with boundary information
* @param text body of 300 response to be split
* @return true if status is 2xx, false otherwise
*/
    parseSiblings: function(contentType, text) {
      var prefixAt = function(idx, prefix) {
        return (text.substr(idx, prefix.length) === prefix);
      };
      var nextChunk = function(lookFor, start_idx) {
        var idx = text.indexOf(lookFor, start_idx);
        if (idx < 0)
          throw("Could not find next chunk");
        return idx;
      };

      var boundary = RiakUtil.get_boundary(contentType);
      var nextBoundary = "--"+boundary+"\r\n";
      var idx = nextChunk(nextBoundary, 0) + nextBoundary.length; // skip preamble
      var last_idx;
      var siblings = [];
      var sibling = {};
      nextBoundary = "\r\n--"+boundary;
      for(;;) {
        // If a header
        if (prefixAt(idx, "\r\n") != true) {
          last_idx = idx;
          idx = nextChunk(": ", last_idx);
var hdr = text.substring(last_idx, idx);
          last_idx = idx + 2;
          idx = nextChunk("\r\n", last_idx);
          var val = text.substring(last_idx, idx);
          if (hdr === 'Content-Type') {
            sibling.contentType = val;
          } else if (hdr === 'Link') {
            sibling.linkHeader = val;
          }
          idx += 2;
} else { // Idx points to \r\n at end of headers, grab the body
          last_idx = idx + 2;
          idx = nextChunk(nextBoundary, last_idx + 2)
          sibling.body = text.substring(last_idx, idx);
          siblings.push(sibling);
          sibling = {};
          idx += nextBoundary.length;
          if (prefixAt(idx, "--\r\n")) // --boundary-- is the end of
            break;
          else if (prefixAt(idx, "\r\n"))
            idx += 2;
          else
            throw("Expecting boundary or end of multipart/mixed");
        }
      }
      return siblings;
    }
  };
}();

/**
* Builds a map/reduce chain and executes it
* @param client RiakClient object
* @param bucketName Riak bucket name
* @param key Riak bucket key (optional)
*/
function RiakMapper(client, bucketName, key) {
  if (bucketName === undefined ||
      client === undefined) {
    throw('Cannot construct RiakMapper without bucketName and client');
  }
  this.client = client;
  this.bucket = bucketName;
  this.key = key;
  this.phases = [];
};

/**
* Add a map phase to a map/reduce job
* @param options - Hash describing the map phase
* @return RiakMapper instance for method chaining fun
*/
RiakMapper.prototype.map = function(options) {
  this.phases.push(this._buildPhase({map: null}, options));
  return this;
};

/**
* Add a map phase to a map/reduce job
* @param options - Hash describing the reduce phase
* @return RiakMapper instance for method chaining fun
*/
RiakMapper.prototype.reduce = function(options) {
  this.phases.push(this._buildPhase({reduce: null}, options));
  return this;
};

/**
* Add a link phase to a map/reduce job
* @param options - Hash describing the link phase
*/
RiakMapper.prototype.link = function(options) {
  this.phases.push(this._buildPhase({link: null}, options));
  return this;
};

/**
* Runs a map/reduce job
* @param timeout - Job timeout (in milliseconds). Defaults to 60000
* @param callback - Function to call when op completes
*
* callback - function(success, request, results)
* @param success - Boolean indicating success or failure
* @param results - JSON decoded results or null
* @param request - XMLHttpRequest object
*/
RiakMapper.prototype.run = function(timeout, callback) {
  if (timeout === undefined || timeout === null) {
    timeout = 60000;
  }
  else if (typeof timeout === 'function') {
    callback = timeout;
    timeout = 60000;
  }
  var mapper = this;
  var job = {'inputs': this._buildInputs(),
'query': this.phases,
'timeout': timeout};
  jQuery.ajax({url: this.client.mapredUrl,
type: 'POST',
data: JSON.stringify(job),
beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', mapper.client.clientId); },
complete: function(req, StatusText) { if (callback !== undefined) {
if (RiakUtil.wasSuccessful(req)) {
callback(true, JSON.parse(req.responseText), req);
}
else {
try {
var error = {error: JSON.parse(req.responseText)};
callback(false, error, req);
}
catch (e) {
callback(false, null, req);
}
} } } });
};

/** Start RiakMapper internals **/
RiakMapper.prototype._buildPhase = function(starter, options) {
  if (typeof options.source === 'function') {
      source = options.source;
      try
      {
        /* Create a string with minimal padding - JSON.parse
* does not like embedded newlines in strings
* and function.toString() on FireFox (on 3.6.3) generates
* a string with embedded newlines.
*/
        options.source = source.toString(-1);
      }
      catch (e)
      {
        options.source = source.toString();
      }
  }
  if ((starter.map === null ||
       starter.reduce === null) && (options.language === null || options.language === undefined)) {
    options.language = 'javascript';
  }
  if (starter.map === null) {
    starter.map = options;
  }
  else if (starter.reduce === null){
    starter.reduce = options;
  }
  else {
    if (options.bucket === null || options.bucket === undefined) {
      options.bucket = this.bucketName;
    }
    starter.link = options;
  }
  return starter;
};

RiakMapper.prototype._buildInputs = function() {
  if (this.key !== null && this.key !== undefined) {
    return [[this.bucket, this.key]];
  }
  else {
    return this.bucket;
  }
}
/** End RiakMapper internals **/

/**
* Models an entry in a Riak bucket
* @param bucketName - Riak bucket name
* @param key - Object's key
* @param client - Owning RiakClient
* @param body - Object's data
* @param contentType - Mime type associated with data
* @param vclock - Riak-assigned vclock
*/
function RiakObject(bucketName, key, client, body, contentType, vclock) {
  if (client === undefined) {
    throw("Cannot construct RiakObject without a client reference");
  }
  this.bucket = bucketName;
  this.key = key;
  this.client = client;
  if (contentType === undefined) {
    this.contentType = 'application/octet-stream';
  }
  else {
    this.contentType = contentType;
  }
  if (contentType === 'application/json') {
    if (body !== undefined) {
      this.body = JSON.parse(body);
    }
    else {
      this.body = '';
    }
  }
  else {
    if (body === undefined) {
      this.body = '';
    }
    else {
      this.body = body;
    }
  }
  this.vclock = vclock;
  this.links = [];
};

/**
* 'Hydrates' a RiakObject from a HTTP request
* @param bucket - Riak bucket name
* @param key - Riak bucket key
* @param client - Owning RiakClient
* @param req - XMLHttpRequest
*/
RiakObject.fromRequest = function(bucket, key, client, req) {
  var contentType = req.getResponseHeader('Content-Type');
  var vclock = req.getResponseHeader('X-Riak-Vclock');
  var linkHeader = req.getResponseHeader('Link');
  var body = req.responseText;
  var retval = new RiakObject(bucket, key, client, body, contentType, vclock);
  retval.setLinks(linkHeader);
  return retval;
};

RiakObject.fromMultipart = function(bucket, key, client, vclock, multipartChunk) {
  var retval = new RiakObject(bucket, key, client, multipartChunk.body, multipartChunk.contentType, vclock);
  retval.setLinks(multipartChunk.linkHeader);
  return retval;
}

/**
* Begins building a map/reduce job which will
* use the current object as input
* @param options - Hash description the map phase
*/
RiakObject.prototype.map = function(options) {
  var mapper = new RiakMapper(this.client, this.bucket, this.key);
  return mapper.map(options);
};

/**
* Begins building a map/reduce job which will
* use the current object as input
* @param options - Hash description the reduce phase
*/
RiakObject.prototype.reduce = function(options) {
  var mapper = new RiakMapper(this.client, this.bucket, this.key);
  return mapper.reduce(options);
};

/**
* Begins building a map/reduce job which will
* use the current object as input
* @param options - Hash description the link phase
*/
RiakObject.prototype.link = function(options) {
  var mapper = new RiakMapper(this.client, this.bucket, this.key);
  return mapper.link(options);
};

/**
* Parses a raw link header and populates the links array
* @param linkHeader - Raw link header string
*/
RiakObject.prototype.setLinks = function(linkHeader) {
  var parsedLinks = new Array();
  if (linkHeader != '') {
    var links = linkHeader.split(",");
    for (var i = 0; i < links.length; i++) {
      var linkParts = links[i].split(';');
      var linkTag = RiakUtil.trim(linkParts[1]);
      var linkTo = RiakUtil.trim(linkParts[0].replace(/Link: ?/, ''));
      linkTo = linkTo.replace(/</, '').replace(/>/, '');
      linkTo = linkTo.replace(/\"/g, '');
      linkTag = linkTag.replace('riaktag=', '');
      linkTag = linkTag.replace(/\"/g, '');
      parsedLinks.push({tag: linkTag.toString(), target: linkTo.toString()});
    }
  }
  this.links = parsedLinks;
};

/**
* Retrieves the links collection
* @return Array of link hashes (e.g. [{tag: 'userInfo', target: '/riak/users/bob'}])
*/
RiakObject.prototype.getLinks = function() {
  return this.links;
};

/**
* Returns the links formatted for the Link header
* @return - Link header string
*/
RiakObject.prototype.getLinkHeader = function() {
  if (this.links.length == 0) {
    return '';
  }
  var header = '';
  for (var i = 0; i < this.links.length; i++) {
      link = this.links[i];
      header = header + '<' + link.target + '>; ';
      if (link.tag === 'rel=up') {
header = header + 'rel="up", ';
      }
      else {
header = header + 'riaktag=\"' + link.tag + '\", ';
      }
  }
  header = header.replace(/\"\"/g, '\"');
  return header.replace(/,\s$/, '');
};

/**
* Adds a link to the object's link collection
* @param link - Pointer to other object (e.g. /riak/foo/bar)
* @param tag - Tag for the link (e.g. 'userInfo')
* @param noDuplicates - Toggle duplicate checking on/off
* @return true if added, false otherwise
*/
RiakObject.prototype.addLink = function(link, tag, noDuplicates) {
  if (link.indexOf('/') == -1) {
    throw('Invalid link: ' + link);
  }
  var retval = true;
  if (noDuplicates === false || noDuplicates === undefined) {
    this.links.push({tag: tag, target:link});
  }
  else {
    var foundDuplicate = false;
    for (var i = 0; i < this.links.length; i++) {
      foundDuplicate = this.links[i].tag === tag &&
this.links[i].target === link;
      if (foundDuplicate) {
retval = false;
break;
      }
    }
    if (!foundDuplicate) {
      this.links.push({tag: tag, target: link});
    }
  }
  return retval;
};

/**
* Removes a link from the links collection based on
* link and tag
* @param link - Pointer to other object
* @param tag - Tag for the link
* @return true if link removed, false if not
*/
RiakObject.prototype.removeLink = function(link, tag) {
  var retval = false;
  var newLinks = this.links.filter(function(l) { return l.link !== link || l.tag !== tag; });
  if (newLinkes.length != this.links.length) {
    retval = true;
    this.links = newLinks;
  }
  return retval;
};

/**
* Resets the links collection to an empty array
*/
RiakObject.prototype.clearLinks = function() {
  this.links = [];
};


/**
* Deletes an object from a Riak bucket
* @param callback - Function to call when op complete
*
* callback - function(success, request)
* @param success - Boolean flag indicating successful removal
* @param request - XMLHTTPRequest object
*/
RiakObject.prototype.remove = function(callback) {
  var object = this;
  jQuery.ajax({url: this.client._buildPath('DELETE', this.bucket, this.key),
type: 'DELETE',
               accepts: RiakUtil.multipart_accepts(),
               dataType: 'multipart',
beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', object.client.clientId);
if (object.vclock !== undefined && object.vclock !== null) {
req.setRequestHeader('X-Riak-Vclock', object.vclock);
}
},
complete: function(req, statusText) { if (callback !== undefined) {
if (RiakUtil.wasSuccessful(req)) {
callback(true, req);
}
else {
callback(false, req);
}
} }});
};

/**
* Store the object in Riak
* @param callback - Function to call when op completes
*
* callback - function(status, object, request)
* @param status - 'status' of the result: 'ok', 'failed', or 'siblings'
* @param object - If status is 'ok', object is an updated RiakObject instance
* If status is 'siblings', object is an array of RiakObject instances
* which the client can pick from to resolve the conflict
* If status is 'failed', object is null
* NOTE: Use the updated version to prevent siblings & vector clock explosion
* @param request - XMLHttpRequest object
*/
RiakObject.prototype.store = function(callback) {
  if (this.contentType === null) {
    throw('RiakObject missing contentType');
  }
  var object = this;
  var objectData = null;
  if (this.contentType === 'application/json') {
    if (this.body !== undefined && this.body !== null) {
      objectData = JSON.stringify(this.body);
    }
    else {
      objectData = this.body;
    }
  }
  else {
    objectData = this.body;
  }
  jQuery.ajax({url: this.client._buildPath('PUT', this.bucket, this.key),
type: 'PUT',
data: objectData,
contentType: this.contentType,
          accepts: RiakUtil.multipart_accepts(),
          dataType: 'multipart',
beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', object.client.clientId);
if (object.vclock !== undefined && object.vclock !== null) {
req.setRequestHeader('X-Riak-Vclock', object.vclock);
}
var linkHeader = object.getLinkHeader();
if (linkHeader !== '') {
req.setRequestHeader('Link', linkHeader);
}
},
complete: function(req, statusText) { object._store(req, callback); } });
};

/** Start RiakObject Internals **/
RiakObject.prototype._store = function(req, callback) {
  if (req.readyState != 4) {
    return;
  }
  if (callback !== undefined && callback !== null) {
    if (req.status == 200 || req.status == 204) {
      callback('ok', RiakObject.fromRequest(this.bucket, this.key, this.client, req), req);
    }
    /* Uh-oh, we've got siblings! */
    else if (req.status == 300) {
      var siblingData = RiakUtil.parseSiblings(req.getResponseHeader('Content-Type'),
req.responseText);
      var vclock = req.getResponseHeader('X-Riak-Vclock');
      var thisObject = this;
        var siblings = [];
      for (var i = 0; i < siblingData.length; i++) {
        var sd = siblingData[i];
        var sib = RiakObject.fromMultipart(thisObject.bucket, thisObject.key,
                                           thisObject.client, vclock, sd);
        siblings.push(sib);
      }
      callback('siblings', siblings, req);
    }
    else {
      callback('failed', null, req);
    }
  }
};

/** End RiakObject Internals **/

/**
* Models a Riak bucket
* @param bucket - Riak bucket name
* @param client - RiakClient reference
*/
function RiakBucket(bucket, client, props) {
  if (client === undefined) {
    throw("Cannot construct RiakBucket without client reference");
  }
  this.name = bucket;
  this.client = client;
  if (props == undefined) {
    this.props = {};
  }
  else {
    this.props = props.props;
  }
};

/**
* "Hydrates" a RiakBucket from a HTTP request
* @param bucketName - Riak bucket name (duh!)
* @param client - RiakClient object
* @param req - Active XMLHttpRequest object
* @return populated RiakBucket instance
*/
RiakBucket.fromRequest = function(bucketName, client, req) {
  var props = JSON.parse(req.responseText);
  return new RiakBucket(bucketName, client, props);
};

/**
* Begins building a map/reduce job which will
* use the entire bucket contents as input
* @param options - Hash description the map phase
* @return RiakMapper object
*/
RiakBucket.prototype.map = function(options) {
  var mapper = new RiakMapper(this.client, this.name);
  return mapper.map(options);
};

/**
* Begins building a map/reduce job which will
* use the entire bucket contents as input
* @param options - Hash description the reduce phase
* @return RiakMapper object
*/
RiakBucket.prototype.reduce = function(options) {
  var mapper = new RiakMapper(this.client, this.name);
  return mapper.reduce(options);
};

/**
* Begins building a map/reduce job which will
* use the entire bucket contents as input
* @param options - Hash description the link phase
* @return RiakMapper object
*/
RiakBucket.prototype.link = function(options) {
  var mapper = new RiakMapper(this.client, this.name);
  options.bucket = this.name;
  return mapper.link(options);
};

/**
* Sets/gets the nValue for this bucket
* @param n -- New nValue (optional)
* @return the current nValue
*/
RiakBucket.prototype.nValue = function(n) {
  var retval = this.props.n_val;
  if (n !== undefined) {
    this.props.n_val = n;
    retval = n;
  }
  return retval;
};

/**
* Enables/disables multiple bucket entries
* @param flag -- true or false
* @return the current setting
*/
RiakBucket.prototype.allowsMultiples = function(flag) {
  var retval = this.props.allow_mult;
  if (flag !== undefined) {
    this.props.allow_mult = flag;
    retval = flag;
  }
  return retval;
};

/**
* Stores bucket
* @param callback - Function to call when op has completed
*
* callback - function(bucket, request)
* @param bucket - Updated bucket or null if store failed
* @param request - XMLHTTPRequest object
*/
RiakBucket.prototype.store = function(callback) {
  var bucket = this;
  var currentProps = {};
  currentProps.props = this.props;
  jQuery.ajax({url: this.client._buildPath('PUT', this.name),
type: 'PUT',
data: JSON.stringify(currentProps),
contentType: 'application/json',
dataType: 'text',
beforeSend: function(req) {
              req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId);
          },
complete: function(req, statusText) { bucket._store(req, callback); } });
};

/**
* Fetch an entry from the bucket
* @param key - Riak bucket key
* @param callback - Function to call when op has completed
*
* callback - function(object, request)
* @param object - RiakObject if found, otherwise null
* @param request - XMLHTTPRequest object
*/
RiakBucket.prototype.get = function(key, callback) {
  var bucket = this;
  jQuery.ajax({url: this.client._buildPath('GET', this.name, key),
type: 'GET',
          accepts: RiakUtil.multipart_accepts(),
          dataType: 'multipart',
beforeSend: function(req) {
              req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId);
          },
complete: function(req, statusText) { bucket._handleGetObject(key, req, callback, false); } });
};

/**
* Fetch an entry from the bucket or create a new one
* if not found
* @param key - Riak bucket key
* @param callback - Function to call when op has completed
*
* callback - function(object, request)
* @param object - RiakObject instance
* @param request - XMLHTTPRequest object
*/
RiakBucket.prototype.get_or_new = function(key, callback) {
  var bucket = this;
  jQuery.ajax({url: this.client._buildPath('GET', this.name, key),
type: 'GET',
          accepts: RiakUtil.multipart_accepts(),
          dataType: 'multipart',
beforeSend: function(req) {
              req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId);
          },
complete: function(req, statusText) { bucket._handleGetObject(key, req, callback, true); } });
};

/**
* Deletes an object from a Riak bucket
* @param key - Riak bucket key
* @param callback - Function to call when op complete
*
* callback - function(success, request)
* @param success - Boolean flag indicating successful removal
* @param request - XMLHTTPRequest object
*/
RiakBucket.prototype.remove = function(key, callback) {
  var bucket = this;
  jQuery.ajax({url: this.client._buildPath('DELETE', bucket.name, key),
type: 'DELETE',
               accepts: RiakUtil.multipart_accepts(),
               dataType: 'multipart',
beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId);
if (bucket.vclock !== undefined && bucket.vclock !== null) {
req.setRequestHeader('X-Riak-Vclock', bucket.vclock);
}
},
complete: function(req, statusText) { if (callback !== undefined) {
if (RiakUtil.wasSuccessful(req)) {
callback(true, req);
}
else {
callback(false, req);
}
} }});
};

/** Start RiakBucket internals **/

RiakBucket.prototype._store = function(req, callback) {
  if (req.readyState != 4) {
    return;
  }
  if (callback !== undefined) {
    if (req.status == 204) {
      this.client.bucket(this.name, callback);
    }
    else {
      callback(null, req);
    }
  }
};

RiakBucket.prototype._handleGetObject = function(key, req, callback, createEmpty) {
  if (req.readyState != 4) {
    return;
  }
  var status = 'failed';
  var object = null;
  if (callback !== null && callback !== undefined) {
    if (req.status == 200) {
      status = 'ok';
      object = RiakObject.fromRequest(this.name, key, this.client, req);
    }
    else if ((req.status == 0 || req.status == 404))
    {
      if (createEmpty === true) {
        status = 'ok';
        object = new RiakObject(this.name, key, this.client);
      }
      /* must not create empty return failed/null */
    }
    /* Uh-oh, we've got siblings! */
    else if (req.status == 300) {
      var siblingData = RiakUtil.parseSiblings(req.getResponseHeader('Content-Type'),
req.responseText);
      var vclock = req.getResponseHeader('X-Riak-Vclock');
      var thisBucket = this;
      var siblings = [];
      for (var i = 0; i < siblingData.length; i++) {
        var sd = siblingData[i];
        var sib = RiakObject.fromMultipart(thisBucket.name, key,
                                           thisBucket.client, vclock, sd);
        siblings.push(sib);
      }
      status = 'siblings'
      object = siblings;
    }
    callback(status, object, req);
  }
};

/** End RiakBucket internals **/


/**
* Entry point for interacting with Riak
* @param baseUrl - URL for 'raw' interface (optional, default: '/riak')
* @param mapredUrl - URL for map/reduce jobs (optional, default: '/mapred')
*/
function RiakClient(baseUrl, mapredUrl) {
  if (baseUrl === undefined) {
    baseUrl = '/riak/';
  }
  else {
    if (baseUrl[0] !== '/') {
      baseUrl = '/' + baseUrl;
    }
    if ((baseUrl.slice(-1) !== '/')) {
      baseUrl += '/';
    }
  }
  this.baseUrl = baseUrl;
  this.clientId = "js_" + RiakUtil.base64Encode(Math.floor(Math.random() * 4294967296));
  if (mapredUrl !== undefined) {
    this.mapredUrl = mapredUrl;
  }
  else {
    this.mapredUrl = '/mapred';
  }
};

/**
* Fetches a bucket from Riak
* Buckets *always* exist so no need to handle
* @param bucket Riak bucket name
* @param callback Function to call when op completes
*
* callback - function(bucket, request)
* @param bucket - RiakBucket instance
* @param request - XMLHTTPRequest object
*/
RiakClient.prototype.bucket = function(bucket, callback) {
  var client = this;
  jQuery.ajax({url: this._buildPath('GET', bucket),
type: 'GET',
contentType: 'application/json',
dataType: 'text',
beforeSend: function(req) {
              req.setRequestHeader('X-Riak-ClientId', this.clientId);
          },
complete: function(req, statusText) { client._handleGetBucket(bucket, req, callback, false); } });
};

/** Begin RiakClient internal functions **/
RiakClient.prototype._handleGetBucket = function(bucketName, req, callback, createEmpty) {
  var bucket = null;
  if (req.readyState != 4) {
    return;
  }
  if (callback !== undefined) {
    if (req.status == 200) {
      bucket = RiakBucket.fromRequest(bucketName, this, req);
    }
    callback(bucket, req);
  }
};

RiakClient.prototype._buildPath = function(method, bucket, key) {
  var path = this.baseUrl + bucket;
  /* Reluctantly adding a cache breaker to each request. FireFox
** sometimes caches XHR responses which triggers failures in the
** unit tests (and presumably real code). See 'bypassing the cache'
** in https://developer-stage.mozilla.org/En/Using_XMLHttpRequest
*/
  var cache_breaker = Math.floor(Math.random() * 4294967296).toString();
  if (key !== undefined) {
    path = path + '/' + key + "?" + cache_breaker;
    if (method === 'PUT') {
      path = path + '&returnbody=true';
    }
  }
  else {
    path = path + "?" + cache_breaker;
    if (method === 'GET') {
      path = path + '&keys=false';
    }
  }
  return path;
};

/** End RiakClient internal Functions **/










/*
http://www.JSON.org/json2.js
2009-09-29

Public Domain.

NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

See http://www.JSON.org/js.html


This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html

USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.


This file creates a global JSON object containing two methods: stringify
and parse.

JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.

replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.

space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.

This method produces a JSON text from a JavaScript value.

When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value

For example, this would serialize Dates as ISO strings.

Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}

return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};

You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.

If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.

Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.

The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.

If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.

Example:

text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'


text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'


JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.

The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.

Example:

// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.

myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});

myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});


This is a reference implementation. You are free to copy, modify, or
redistribute.
*/

/*jslint evil: true, strict: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear() + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate()) + 'T' +
                 f(this.getUTCHours()) + ':' +
                 f(this.getUTCMinutes()) + ':' +
                 f(this.getUTCSeconds()) + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = { // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i, // The loop counter.
            k, // The member key.
            v, // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());